#pragma once
#include<iostream>
#include<functional>
#include<unordered_map>
#include<fcntl.h>
#include"Sock.hpp"
#include"Log.hpp"
#include"Err.hpp"
#include"Epoller.hpp"
#include"Util.hpp"
#include"Protocol.hpp"


// #define DEBUG_M
namespace tcpserver
{
    static const uint16_t defaultport = 8080;
    static const int num = 64;
    class Connection;
    class TcpServer;
    using func_t = std::function<void(Connection*)>;
    // using handler_t =std::function<void(const std::string&)>;


    // 我们虽然使用使用轮询可以把这一轮的数据都全部读取上来！
    // 但是能获取一个完整的请求吗？——不一定！因为请求可能会被拆分成很多个部分！
    // 所以本轮将数据读取完毕是ET的要求！而获取完整的请求是协议的要求！
    // 但是这就衍生出了一个问题！如果一轮不能获取一个完整的请求！必须多轮读取才能获取
    // 一个完整的请求！我们应该把请求的数据还不完整的数据放在哪里？
    // 所以每一个文件文件描述符的输入输出的用户级缓冲区我们都要带上！
    //我们我们要将fd进行封装，让其有对应的用户级缓冲区空间！这样子就可以将数据存在用户级缓冲区里面！
    //下一轮读取就可以将剩下的数据拼接上去即可？！
    class Connection
    {
    public:
        Connection(int sock,TcpServer* tsp):sock_(sock),tsp_(tsp)
        {}
        ~Connection()
        {}

        void Register(func_t r,func_t s,func_t e)//注册方法！
        {
            recver_ = r;
            sender_ = s;
            excepter_ = e; 
        }

        void Close()
        {
            close(sock_);
        }
    public:
        int sock_;
        std::string inbuffer_;//输入缓冲区
        std::string outbuffer_;//输出缓冲区！
        //其实使用string不太好，因为如果是图片，音频等数据就不好处理了！但是我们这里为了方便演示就这样使用了

        //对于一个文件描述符我们要提供三种回调方法！
        func_t recver_;//从listensock_中读取！
        func_t sender_;//向listensock_中写！
        func_t excepter_;//处理listensock_IO的时候的异常事件！

        TcpServer *tsp_; // 回指指针！可以省略
    };
    //未来服务器里面会存在很多Connection对象！
    //所以我们也要用特定的数据结构管理Connection！——我们这里采用unoredered_map来进行管理



    class TcpServer
    {
    private:
        void Recver(Connection* conn)
        {
            for(;;)//循环读取
            {
                char buffer[1024];
                ssize_t s = recv(conn->sock_,buffer,sizeof(buffer)-1,0);
                if(s > 0)//读取成功
                {
                    buffer[s] = 0;
                    conn->inbuffer_ +=buffer;//将数据放入接收缓冲区里面！
                }
                else  if(s == 0)//客户端关闭
                {
                    if(conn->excepter_)//这时候就出现异常了！我们一律转到excepter函数处理异常！
                    {
                        conn->excepter_(conn);
                        return;
                    }
                }
                else 
                {
                    if(errno == EAGAIN || errno == EWOULDBLOCK)//说明读取失败是因为底层没有数据了
                        break;
                    else if(errno == EINTR)//因为信号导致读取中断！
                        continue;
                    else //真的出现错误了
                    {
                        if (conn->excepter_) // 这时候就出现异常了！我们一律转到excepter函数处理异常！
                        {
                            conn->excepter_(conn);
                            return;
                        }
                    }
                }
            }

#ifdef DEBUG_M
            LogMessage(DEBUG,"%d——>%s",conn->sock_,conn->inbuffer_.c_str());
#endif
            service_(conn); // 把Connection对象暴露出去！
        }

        void Sender(Connection* conn)
        {
            while (true)
            {
                ssize_t s = send(conn->sock_, conn->outbuffer_.c_str(), conn->outbuffer_.size(), 0);
                if (s >= 0)//如果>0 ，因为是非阻塞如果发送的字符串为0，那么返回值就是0
                {
                    if (conn->outbuffer_.empty())//这才是真的为空
                    {
                        // EnableReadWrite(conn,true,false);
                        //如果不写在sender函数里面就要多一个这个用于关闭！
                        //因为这里进行写事件关闭是因为Calculate函数只会在收到请求后被调用
                        //如果一次没有发送完毕！后续就没有人重新调用Calculate函数了！来对写事件进行关闭了！
                        //所以为了防止二次发送的情况，所以我们就直接在发送完毕后就关闭写事件
  
                        
                        break; // 如果为空就说明发送完毕了！
                    }
                    else
                        conn->outbuffer_.erase(0,s);//删除发送的字节
                }
                else
                {
                    if (errno == EAGAIN || errno == EWOULDBLOCK) // send的因为发送条件不满足也是会设置成同样的错误码！
                        break;
                    else if (errno == EINTR) // 因为信号导致发送中断！
                        continue;
                    else // 真的出现错误了
                    {
                        if (conn->excepter_) // 这时候就出现异常了！我们一律转到excepter函数处理异常！
                        {
                            conn->excepter_(conn);
                            return;
                        }
                    }
                }
            }

            if (!conn->outbuffer_.empty()) 
            {
                conn->tsp_->EnableReadWrite(conn, true, true);
            }
            else 
            {
                conn->tsp_->EnableReadWrite(conn, true, false);
            }
            //其实还有一种更简单写法就是写在这里！发送后就去判断要不要关闭！这样就不用烦恼了！
        }
        void Excepter(Connection *conn)
        {
            LogMessage(DEBUG,"Excepter begin!");
            epoller_.Control(conn->sock_,0,EPOLL_CTL_DEL);//从底层移除
            conn->Close();//关闭文件描述符！
            connections_.erase(conn->sock_);//删除映射关闭！
            delete conn;//释放对方！
            
            LogMessage(DEBUG,"Excepter end!");
        }

        void Accepter(Connection *conn)
        {
#ifdef DEBUG_M
            LogMessage(DEBUG,"Accepter! start!");
#endif 
            for(;;)//要不断的读取！
            {
                std::string clientip;
                uint16_t clientport;
                int err;
                int sock = listensock_.Accept(&clientip, &clientport,&err); // 获取新的套接字

                if(sock > 0)
                {
                    // 我们拿上来了不能直接读取！也不能直接写入
                    // 要构建一个Connnection对象，然后添加进epoll里面！
                    // 我们已经封装好了一个接口！直接调用即可！
                    AddConnection(
                        sock, EPOLLIN | EPOLLET,
                                  std::bind(&TcpServer::Recver, this, std::placeholders::_1),
                                  std::bind(&TcpServer::Sender, this, std::placeholders::_1),
                                  std::bind(&TcpServer::Excepter, this, std::placeholders::_1)
                                  );
                    // 因为类内函数还有一个隐藏的this指针参数！这样和func_t的函数类型就不匹配了！
                    // 我们可以使用bind固定参数，生成一个新的可调用对象！
                    // 用于适应参数来适应原对象的参数列表！
                    LogMessage(NORMAL, "get a new link success!,info [%s:%d]",clientip.c_str(),clientport); 

                }
                else //读取出错！
                {
                    if(err == EAGAIN || EWOULDBLOCK)//这里出错只是因为底层没有数据！
                        break;//说明已经读取完毕了跳出循环！
                    else if(err == EINTR)//读取的时候被信号中断了！
                        continue;//继续读取！
                    else//这里就是真正出错了！
                        break;
                }
                
            }
            //因为我们listenock是ET模式的！所以每次连接一来都只会通知一次！
            //所以只会调用一次Accepter函数！但是我们能保证一次只来一个连接吗？
            //不能！所以我们就必须循环读取一次性将所有连接都读取上来！

#ifdef DEBUG_M
            LogMessage(DEBUG,"Accepter! end!");
#endif

        }

        void AddConnection(int sock,uint32_t event,func_t recver,func_t sender,func_t excepter)
        {
            //1.因为ET模式下的fd必须是非阻塞！所以要将listensock成非阻塞！
            if (event & EPOLLET) Util::SetNonBlock(sock);
            //2.首先要为Sock创建Connection对象并初始化！ 
            Connection* con = new Connection(sock,this);

            //2.1给对应sock设置对应的回调方法！
            con->Register(recver,sender,excepter);

            
            //3.其次将Sock和要关心的事件注释到epoll中！
            bool n = epoller_.AddEvent(sock, event); // 将sock放入epoll
            if(n == false)
            {
                LogMessage(FATAL, "sock add failed!,code :%d,strerror :%s",errno,strerror(errno));
            }
            else 
            {
                LogMessage(NORMAL, "Connection add success!");

                // 将kv添加到Connections中
                connections_[sock] = con;
                // connections_.insert({sock,con});
                // connections_.insert(std::pair<int,Connection*>(sock,con));

#ifdef DEBUG_M
                LogMessage(DEBUG, "Add new sock :%d in epoll and unordered_map", sock);
#endif 
            }
        }

        bool IsConnectionExits(int sock)
        {
            auto iter = connections_.find(sock);
            return iter != connections_.end();
        }
        void Loop(int timeout)
        {
            int n = epoller_.Wait(revs_,revs_num_,timeout);//来获取已经就绪的事件
            
            for (int i = 0; i < n; i++)
            {
                int sock = revs_[i].data.fd;
                uint32_t  events = revs_[i].events;

                if(events & EPOLLERR)//当前是异常事件
                    events |= (EPOLLIN | EPOLLOUT);

                if(events & EPOLLHUP)//对方关闭掉连接！
                    events |= (EPOLLIN | EPOLLOUT);

                //一旦出现异常！就将读事件和写事件都同时就绪！
                //这样子我们就可以将异常问题，转化为读写问题！——因为读写本身就要处理异常！
                //那么只要我们将读写异常处理了！那么这个异常也就被处理了！
                


                // //开始事件派发！
                // if((events & EPOLLIN) && (sock == listensock_.Fd()))//监听事件就绪！
                // {
                //     //通过Connections找到sock对应的Connection对象！
                //     //然后使用Connection对象的自己的回调方法来进行读取
                //     connections_[sock]->recver_(connections_[sock]);
                //     //因为listensock绑定的方法是Accept读方法！所以最后调用的就是Accept函数！
                // }

                // if((events & EPOLLIN) & sock != listensock_.Fd())//普通的文件描述符！
                //     connections_[sock]->recver_(connections_[sock]);
                // //我们发现在普通套接字和listensock套接字使用读取方法的形式是一致的！
                // //所以我们其实可以将其合并！

                if((events & EPOLLIN) && IsConnectionExits(sock))//读事件
                {
#ifdef DEBUG_M
                    LogMessage(DEBUG,"开始读事件");
#endif
                    if(connections_[sock]->recver_!= nullptr)//防止没有注册方法！
                        connections_[sock]->recver_(connections_[sock]);//只要读事件就绪就根据文件描述符指向其对应的方法！
                }


                if((events & EPOLLOUT) && IsConnectionExits(sock)  )//写事件
               {
#ifdef DEBUG_M
                    LogMessage(DEBUG,"开始写事件");
#endif
                    if(connections_[sock]->sender_!= nullptr)
                        connections_[sock]->sender_(connections_[sock]);
                }
                
            }
        }


    public:
        TcpServer(func_t func, uint16_t port = defaultport)
            : port_(port),revs_(nullptr),service_(func)
        {}

        void InitServer()
        {
            //创立,绑定，设置为监听
            listensock_.Socket();
            listensock_.Bind(port_);
            listensock_.Listen();

            //创建epoll模型！将listensock添加进epoll模型里面
            epoller_.Creat();

            //未来listensock只关心读取！所以也只要传入读取的方法即可！
            // AddConnection(listensock_.Fd(),EPOLLIN | EPOLLET,Accepter,nullptr,nullptr);
            //这样写是有问题的！
            AddConnection(listensock_.Fd(),EPOLLIN | EPOLLET,\
            std::bind(&TcpServer::Accepter,this,std::placeholders::_1),nullptr,nullptr);
            //因为Accepter还有一个隐藏的this指针参数！这样和func_t的函数类型就不匹配了！
            //我们可以使用bind固定参数，生成一个新的可调用对象！
            //用于适应参数来适应原对象的参数列表！

            revs_ = new struct epoll_event[num];//存放就绪时事件的空间！
            revs_num_ = num;
        }

        //要实现一个事件派发！
        void Dispatch()
        {
            int timeout = -1;
            while(true)
            {
                Loop(timeout);
                LogMessage(DEBUG,"time out....");
            }
        }

        void EnableReadWrite(Connection* conn,bool readable,bool writeable)
        {
            uint32_t event = (readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0) | EPOLLET;
            epoller_.Control(conn->sock_,event,EPOLL_CTL_MOD);
        }


        ~TcpServer()
        {
            listensock_.Close();
            epoller_.Close();
            if(revs_ != nullptr) delete[] revs_;
        }

    private:
        uint16_t port_;
        Epoller epoller_;
        Sock listensock_;
        struct epoll_event* revs_;
        int revs_num_;
        std::unordered_map<int,Connection*> connections_;//通过sock快速索引！
        // handler_t handler_;
        func_t service_;

    };
}